/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 1998-2017 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include "netlist.h"

#include <climits>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <typeinfo>

#include "compiler.h"
#include "config.h"
#include "ivl_assert.h"
#include "netclass.h"
#include "netdarray.h"
#include "netenum.h"
#include "netmisc.h"
#include "netparray.h"
#include "netqueue.h"
#include "netstruct.h"
#include "netvector.h"

ostream& operator<<(ostream& o, NetNet::Type t) {
  switch (t) {
    case NetNet::NONE:
      o << "net_none";
      break;
    case NetNet::IMPLICIT:
      o << "wire /*implicit*/";
      break;
    case NetNet::IMPLICIT_REG:
      o << "reg /*implicit*/";
      break;
    case NetNet::INTEGER:
      o << "integer";
      break;
    case NetNet::REG:
      o << "reg";
      break;
    case NetNet::SUPPLY0:
      o << "supply0";
      break;
    case NetNet::SUPPLY1:
      o << "supply1";
      break;
    case NetNet::TRI:
      o << "tri";
      break;
    case NetNet::TRI0:
      o << "tri0";
      break;
    case NetNet::TRI1:
      o << "tri1";
      break;
    case NetNet::TRIAND:
      o << "triand";
      break;
    case NetNet::TRIOR:
      o << "trior";
      break;
    case NetNet::WAND:
      o << "wand";
      break;
    case NetNet::WOR:
      o << "wor";
      break;
    case NetNet::WIRE:
      o << "wire";
      break;
    case NetNet::UNRESOLVED_WIRE:
      o << "uwire";
  }
  return o;
}

unsigned count_signals(const Link& pin) {
  unsigned count = 0;

  const Nexus* nex = pin.nexus();
  for (const Link* clnk = nex->first_nlink(); clnk; clnk = clnk->next_nlink()) {
    const NetPins* cur;
    unsigned cpin;
    clnk->cur_link(cur, cpin);
    if (dynamic_cast<const NetNet*>(cur)) count += 1;
  }

  return count;
}

const NetNet* find_link_signal(const NetObj* net, unsigned pin,
                               unsigned& bidx) {
  const Nexus* nex = net->pin(pin).nexus();

  for (const Link* clnk = nex->first_nlink(); clnk; clnk = clnk->next_nlink()) {
    const NetPins* cur;
    unsigned cpin;
    clnk->cur_link(cur, cpin);

    const NetNet* sig = dynamic_cast<const NetNet*>(cur);
    if (sig) {
      bidx = cpin;
      return sig;
    }
  }

  return 0;
}

Link* find_next_output(Link* lnk) {
  Link* cur = lnk->next_nlink();
  while (cur != lnk) {
    if (cur->get_dir() == Link::OUTPUT) return cur;

    cur = cur->next_nlink();
    if (cur == 0) cur = lnk->nexus()->first_nlink();
  }

  return 0;
}

void NetPins::devirtualize_pins(void) {
  if (pins_) return;
  if (npins_ > array_size_limit) {
    cerr << get_fileline() << ": error: pin count " << npins_ << " exceeds "
         << array_size_limit << " (set by -pARRAY_SIZE_LIMIT)" << endl;
    assert(0);
  }
  if (debug_optimizer && npins_ > 1000)
    cerr << "debug: devirtualizing " << npins_ << " pins." << endl;

  pins_ = new Link[npins_];
  pins_[0].pin_zero_ = true;
  pins_[0].node_ = this;
  pins_[0].dir_ = default_dir_;

  for (unsigned idx = 1; idx < npins_; idx += 1) {
    pins_[idx].pin_zero_ = false;
    pins_[idx].pin_ = idx;
    pins_[idx].dir_ = default_dir_;
  }
}

bool NetPins::pins_are_virtual(void) const { return pins_ == NULL; }

NetPins::NetPins(unsigned npins) : npins_(npins) {
  default_dir_ = Link::PASSIVE;
  pins_ = NULL;                                   // Wait until someone asks.
  if (disable_virtual_pins) devirtualize_pins();  // Ask.  Bummer.
}

NetPins::~NetPins() {
  if (pins_) {
    assert(pins_[0].node_ == this);
    assert(pins_[0].pin_zero_);
    delete[] pins_;
  }
}

Link& NetPins::pin(unsigned idx) {
  if (!pins_) devirtualize_pins();
  if (idx >= npins_) {
    cerr << get_fileline() << ": internal error: pin(" << idx << ")"
         << " out of bounds(" << npins_ << ")" << endl;
    cerr << get_fileline()
         << ":               : typeid=" << typeid(*this).name() << endl;
  }

  assert(idx < npins_);
  assert(idx == 0 ? (pins_[0].pin_zero_ && pins_[0].node_ == this)
                  : pins_[idx].pin_ == idx);

  return pins_[idx];
}

const Link& NetPins::pin(unsigned idx) const {
  if (!pins_ && !disable_virtual_pins) {
    cerr << get_fileline()
         << ": internal error: pin is unexpectedly"
            " virtual, try again with -pDISABLE_VIRTUAL_PINS=true"
         << endl;
    assert(0);
  }
  assert(pins_);
  assert(idx < npins_);
  assert(idx == 0 ? (pins_[0].pin_zero_ && pins_[0].node_ == this)
                  : pins_[idx].pin_ == idx);
  return pins_[idx];
}

void NetPins::set_default_dir(Link::DIR d) { default_dir_ = d; }

bool NetPins::is_linked(void) {
  bool linked_flag = false;
  if (pins_ == NULL) return false;
  for (unsigned u = 0; u < npins_; u++) {
    if (pins_[u].is_linked()) {
      linked_flag = true;
      break;
    }
  }
  return linked_flag;
}

NetObj::NetObj(NetScope* s, perm_string n, unsigned np)
    : NetPins(np), scope_(s), name_(n), delay1_(0), delay2_(0), delay3_(0) {
  /* Don't
  ivl_assert(*this, np > 0);
   * because it would happen before we get to print a useful
   * message in the NetNet constructor
   */
}

NetObj::~NetObj() {}

NetScope* NetObj::scope() { return scope_; }

const NetScope* NetObj::scope() const { return scope_; }

NetNode::NetNode(NetScope* s, perm_string n, unsigned npins)
    : NetObj(s, n, npins), node_next_(0), node_prev_(0), design_(0) {}

NetNode::~NetNode() {
  if (design_) design_->del_node(this);
}

NetBranch::NetBranch(ivl_discipline_t dis) : NetPins(2), IslandBranch(dis) {
  pin(0).set_dir(Link::PASSIVE);
  pin(1).set_dir(Link::PASSIVE);
}

NetBranch::~NetBranch() {}

NetBus::NetBus(NetScope* s, unsigned pin_count__)
    : NetObj(s, perm_string::literal(""), pin_count__) {
  for (unsigned idx = 0; idx < pin_count__; idx += 1) {
    pin(idx).set_dir(Link::PASSIVE);
  }
}

NetBus::~NetBus() {}

unsigned NetBus::find_link(const Link& that) const {
  unsigned ptr = 0;

  while (ptr < pin_count()) {
    if (pin(ptr).is_linked(that)) return ptr;

    ptr += 1;
  }
  return ptr;
}

NetDelaySrc::NetDelaySrc(NetScope* s, perm_string n, unsigned npins,
                         bool condit_src, bool conditional)
    : NetObj(s, n, npins + (condit_src ? 1 : 0)) {
  condit_flag_ = false;
  conditional_ = conditional;
  posedge_ = false;
  negedge_ = false;
  for (unsigned idx = 0; idx < npins; idx += 1) {
    pin(idx).set_dir(Link::INPUT);
  }

  if (condit_src) {
    condit_flag_ = true;
    pin(npins).set_dir(Link::INPUT);
  }
}

NetDelaySrc::~NetDelaySrc() {}

void NetDelaySrc::set_delays(uint64_t del) {
  for (unsigned idx = 0; idx < 12; idx += 1) transition_delays_[idx] = del;
}

void NetDelaySrc::set_delays(uint64_t trise, uint64_t tfall) {
  transition_delays_[IVL_PE_01] = trise;
  transition_delays_[IVL_PE_10] = tfall;
  transition_delays_[IVL_PE_0z] = trise;
  transition_delays_[IVL_PE_z1] = trise;
  transition_delays_[IVL_PE_1z] = tfall;
  transition_delays_[IVL_PE_z0] = tfall;
  transition_delays_[IVL_PE_0x] = trise;
  transition_delays_[IVL_PE_x1] = trise;
  transition_delays_[IVL_PE_1x] = tfall;
  transition_delays_[IVL_PE_x0] = tfall;
  transition_delays_[IVL_PE_xz] = max(trise, tfall);
  transition_delays_[IVL_PE_zx] = min(trise, tfall);
}

void NetDelaySrc::set_delays(uint64_t trise, uint64_t tfall, uint64_t tz) {
  transition_delays_[IVL_PE_01] = trise;
  transition_delays_[IVL_PE_10] = tfall;
  transition_delays_[IVL_PE_0z] = tz;
  transition_delays_[IVL_PE_z1] = trise;
  transition_delays_[IVL_PE_1z] = tz;
  transition_delays_[IVL_PE_z0] = tfall;
  transition_delays_[IVL_PE_0x] = min(trise, tz);
  transition_delays_[IVL_PE_x1] = trise;
  transition_delays_[IVL_PE_1x] = min(tfall, tz);
  transition_delays_[IVL_PE_x0] = tfall;
  transition_delays_[IVL_PE_xz] = tz;
  transition_delays_[IVL_PE_zx] = min(trise, tfall);
}

void NetDelaySrc::set_delays(uint64_t t01, uint64_t t10, uint64_t t0z,
                             uint64_t tz1, uint64_t t1z, uint64_t tz0) {
  transition_delays_[IVL_PE_01] = t01;
  transition_delays_[IVL_PE_10] = t10;
  transition_delays_[IVL_PE_0z] = t0z;
  transition_delays_[IVL_PE_z1] = tz1;
  transition_delays_[IVL_PE_1z] = t1z;
  transition_delays_[IVL_PE_z0] = tz0;
  transition_delays_[IVL_PE_0x] = min(t01, t0z);
  transition_delays_[IVL_PE_x1] = max(t01, tz1);
  transition_delays_[IVL_PE_1x] = min(t10, t1z);
  transition_delays_[IVL_PE_x0] = max(t10, tz0);
  transition_delays_[IVL_PE_xz] = max(t1z, t0z);
  transition_delays_[IVL_PE_zx] = min(tz1, tz0);
}

void NetDelaySrc::set_delays(uint64_t t01, uint64_t t10, uint64_t t0z,
                             uint64_t tz1, uint64_t t1z, uint64_t tz0,
                             uint64_t t0x, uint64_t tx1, uint64_t t1x,
                             uint64_t tx0, uint64_t txz, uint64_t tzx) {
  transition_delays_[IVL_PE_01] = t01;
  transition_delays_[IVL_PE_10] = t10;
  transition_delays_[IVL_PE_0z] = t0z;
  transition_delays_[IVL_PE_z1] = tz1;
  transition_delays_[IVL_PE_1z] = t1z;
  transition_delays_[IVL_PE_z0] = tz0;
  transition_delays_[IVL_PE_0x] = t0x;
  transition_delays_[IVL_PE_x1] = tx1;
  transition_delays_[IVL_PE_1x] = t1x;
  transition_delays_[IVL_PE_x0] = tx0;
  transition_delays_[IVL_PE_xz] = txz;
  transition_delays_[IVL_PE_zx] = tzx;
}

uint64_t NetDelaySrc::get_delay(unsigned idx) const {
  assert(idx < 12);
  return transition_delays_[idx];
}

void NetDelaySrc::set_posedge() { posedge_ = true; }

void NetDelaySrc::set_negedge() { negedge_ = true; }

bool NetDelaySrc::is_posedge() const { return posedge_; }

bool NetDelaySrc::is_negedge() const { return negedge_; }

unsigned NetDelaySrc::src_count() const {
  if (condit_flag_)
    return pin_count() - 1;
  else
    return pin_count();
}

Link& NetDelaySrc::src_pin(unsigned idx) {
  ivl_assert(*this, idx < src_count());
  return pin(idx);
}

const Link& NetDelaySrc::src_pin(unsigned idx) const {
  ivl_assert(*this, idx < src_count());
  return pin(idx);
}

bool NetDelaySrc::is_condit() const { return conditional_; }

bool NetDelaySrc::has_condit() const { return condit_flag_; }

Link& NetDelaySrc::condit_pin() {
  ivl_assert(*this, condit_flag_);
  return pin(pin_count() - 1);
}

const Link& NetDelaySrc::condit_pin() const {
  ivl_assert(*this, condit_flag_);
  return pin(pin_count() - 1);
}

PortType::Enum PortType::merged(Enum lhs, Enum rhs) {
  if (lhs == NOT_A_PORT || rhs == NOT_A_PORT) return NOT_A_PORT;
  if (lhs == PIMPLICIT) return rhs;
  if (rhs == PIMPLICIT) return lhs;
  if (lhs == rhs) {
    return lhs;
  }
  return PINOUT;
}

void NetNet::initialize_dir_() {
  Link::DIR dir = Link::PASSIVE;

  switch (type_) {
    case REG:
    case IMPLICIT_REG:
    case SUPPLY0:
    case SUPPLY1:
    case TRI0:
    case TRI1:
      dir = Link::OUTPUT;
      break;
    default:
      break;
  }

  if (pins_are_virtual()) {
    if (0) cerr << "NetNet setting Link default dir" << endl;
    set_default_dir(dir);
  } else {
    for (unsigned idx = 0; idx < pin_count(); idx += 1) {
      pin(idx).set_dir(dir);
    }
  }
}

static unsigned calculate_count(const list<netrange_t>& unpacked) {
  unsigned long sum = 1;
  for (list<netrange_t>::const_iterator cur = unpacked.begin();
       cur != unpacked.end(); ++cur) {
    // Special case: If there are any undefined dimensions,
    // then give up on trying to create pins for the net.
    if (!cur->defined()) return 0;

    sum *= cur->width();
  }

  if (sum >= UINT_MAX) return 0;

  return sum;
}

template <class T>
static unsigned calculate_count(T* type) {
  long wid = type->packed_width();
  if (wid >= 0)
    return wid;
  else
    return 1;
}

void NetNet::calculate_slice_widths_from_packed_dims_(void) {
  ivl_assert(*this, net_type_);
  slice_dims_ = net_type_->slice_dimensions();

  // Special case: There are no actual packed dimensions, so
  // build up a fake dimension of "1".
  if (slice_dims_.empty()) {
    slice_wids_.resize(1);
    slice_wids_[0] = net_type_->packed_width();
    return;
  }

  slice_wids_.resize(slice_dims_.size());

  ivl_assert(*this, !slice_wids_.empty());
  slice_wids_[0] = netrange_width(slice_dims_);
  vector<netrange_t>::const_iterator cur = slice_dims_.begin();
  for (size_t idx = 1; idx < slice_wids_.size(); idx += 1, ++cur) {
    slice_wids_[idx] = slice_wids_[idx - 1] / cur->width();
  }
}

const list<netrange_t> NetNet::not_an_array;

NetNet::NetNet(NetScope* s, perm_string n, Type t,
               const list<netrange_t>& unpacked, ivl_type_t use_net_type)
    : NetObj(s, n, calculate_count(unpacked)),
      type_(t),
      port_type_(NOT_A_PORT),
      local_flag_(false),
      net_type_(use_net_type),
      discipline_(0),
      unpacked_dims_(unpacked.size()),
      eref_count_(0),
      lref_count_(0) {
  calculate_slice_widths_from_packed_dims_();
  size_t idx = 0;
  for (list<netrange_t>::const_iterator cur = unpacked.begin();
       cur != unpacked.end(); ++cur, idx += 1) {
    unpacked_dims_[idx] = *cur;
  }
  assert(idx == unpacked_dims_.size());

  ivl_assert(*this, s);
  if (pin_count() == 0) {
    cerr << "Invalid array dimensions: " << unpacked << endl;
    ivl_assert(*this, 0);
  }

  initialize_dir_();

  s->add_signal(this);
}

/*
 * When we create a netnet for a packed struct, create a single
 * vector with the msb_/lsb_ chosen to name enough bits for the entire
 * packed structure.
 */
NetNet::NetNet(NetScope* s, perm_string n, Type t, netstruct_t* ty)
    : NetObj(s, n, 1),
      type_(t),
      port_type_(NOT_A_PORT),
      local_flag_(false),
      net_type_(ty),
      discipline_(0),
      eref_count_(0),
      lref_count_(0) {
  // XXXX packed_dims_.push_back(netrange_t(calculate_count(ty)-1, 0));
  calculate_slice_widths_from_packed_dims_();

  initialize_dir_();

  s->add_signal(this);
}

NetNet::NetNet(NetScope* s, perm_string n, Type t, netdarray_t* ty)
    : NetObj(s, n, 1),
      type_(t),
      port_type_(NOT_A_PORT),
      local_flag_(false),
      net_type_(ty),
      discipline_(0),
      eref_count_(0),
      lref_count_(0) {
  initialize_dir_();

  s->add_signal(this);
}

NetNet::NetNet(NetScope* s, perm_string n, Type t, netvector_t* ty)
    : NetObj(s, n, 1),
      type_(t),
      port_type_(NOT_A_PORT),
      local_flag_(false),
      net_type_(ty),
      discipline_(0),
      eref_count_(0),
      lref_count_(0) {
  calculate_slice_widths_from_packed_dims_();

  initialize_dir_();

  s->add_signal(this);
}

NetNet::~NetNet() {
  if (eref_count_ > 0) {
    cerr << get_fileline() << ": internal error: attempt to delete "
         << "signal ``" << name() << "'' which has "
         << "expression references." << endl;
    dump_net(cerr, 4);
  }
  assert(eref_count_ == 0);
  if (lref_count_ > 0) {
    cerr << get_fileline() << ": internal error: attempt to delete "
         << "signal ``" << name() << "'' which has "
         << "assign references." << endl;
    dump_net(cerr, 4);
  }
  assert(lref_count_ == 0);
  if (scope()) scope()->rem_signal(this);
}

NetNet::Type NetNet::type() const { return type_; }

void NetNet::type(NetNet::Type t) {
  if (type_ == t) return;

  type_ = t;

  initialize_dir_();
}

NetNet::PortType NetNet::port_type() const { return port_type_; }

void NetNet::port_type(NetNet::PortType t) { port_type_ = t; }

int NetNet::get_module_port_index() const { return port_index_; }

void NetNet::set_module_port_index(unsigned idx) {
  port_index_ = idx;
  assert(port_index_ >= 0);
}

ivl_variable_type_t NetNet::data_type() const {
  ivl_assert(*this, net_type_);
  return net_type_->base_type();
}

bool NetNet::get_signed() const {
  ivl_assert(*this, net_type_);
  return net_type_->get_signed();
}

bool NetNet::get_isint() const {
  if (const netvector_t* vec = dynamic_cast<const netvector_t*>(net_type_))
    return vec->get_isint();
  else
    return false;
}

bool NetNet::get_scalar() const {
  if (const netvector_t* vec = dynamic_cast<const netvector_t*>(net_type_))
    return vec->get_scalar();
  else
    return false;
}

const netenum_t* NetNet::enumeration(void) const {
  return dynamic_cast<const netenum_t*>(net_type_);
}

const netstruct_t* NetNet::struct_type(void) const {
  ivl_type_t cur_type = net_type_;
  while (cur_type) {
    if (const netdarray_t* da = dynamic_cast<const netdarray_t*>(cur_type)) {
      cur_type = da->element_type();
      continue;
    }
    if (const netparray_t* da = dynamic_cast<const netparray_t*>(cur_type)) {
      cur_type = da->element_type();
      continue;
    }
    if (const netstruct_t* st = dynamic_cast<const netstruct_t*>(cur_type))
      return st;
    else
      return 0;
  }

  assert(0);
  return 0;
}

const netdarray_t* NetNet::darray_type(void) const {
  return dynamic_cast<const netdarray_t*>(net_type_);
}

const netqueue_t* NetNet::queue_type(void) const {
  return dynamic_cast<const netqueue_t*>(net_type_);
}

const netclass_t* NetNet::class_type(void) const {
  return dynamic_cast<const netclass_t*>(net_type_);
}

/*
 * "depth" is the number of index expressions that the user is using
 * to index this identifier. So consider if Net was declared like so:
 *
 *   reg [5:0][3:0] foo;
 *
 * In this case, slice_width(2) == 1  (slice_width(N) where N is the
 * number of dimensions will always be 1.) and represents
 * $bits(foo[a][b]). Then, slice_width(1)==4 ($bits(foo[a]) and
 * slice_width(0)==24.
 *
 * NOTE: The caller should already have accounted for unpacked
 * dimensions. The "depth" is only for the packed dimensions.
 */
unsigned long NetNet::slice_width(size_t depth) const {
  if (depth > slice_wids_.size()) return 0;
  if (depth == slice_wids_.size()) return 1;
  return slice_wids_[depth];
}

ivl_discipline_t NetNet::get_discipline() const { return discipline_; }

void NetNet::set_discipline(ivl_discipline_t dis) {
  ivl_assert(*this, discipline_ == 0);
  discipline_ = dis;
}

bool NetNet::sb_is_valid(const list<long>& indices, long sb) const {
  ivl_assert(*this, indices.size() + 1 == packed_dims().size());
  assert(packed_dims().size() == 1);
  const netrange_t& rng = packed_dims().back();
  if (rng.get_msb() >= rng.get_lsb())
    return (sb <= rng.get_msb()) && (sb >= rng.get_lsb());
  else
    return (sb <= rng.get_lsb()) && (sb >= rng.get_msb());
}

long NetNet::sb_to_idx(const list<long>& indices, long sb) const {
  ivl_assert(*this, indices.size() + 1 == packed_dims().size());

  vector<netrange_t>::const_iterator pcur = packed_dims().end();
  --pcur;

  long acc_off;
  long acc_wid = pcur->width();
  if (pcur->get_msb() >= pcur->get_lsb())
    acc_off = sb - pcur->get_lsb();
  else
    acc_off = pcur->get_lsb() - sb;

  // The acc_off is the position within the innermost
  // dimension. If this is a multi-dimension packed array then
  // we need to add in the canonical address of the current slice.
  if (!indices.empty()) {
    list<long>::const_iterator icur = indices.end();
    do {
      --icur;
      --pcur;

      long tmp_off;
      if (pcur->get_msb() >= pcur->get_lsb())
        tmp_off = *icur - pcur->get_lsb();
      else
        tmp_off = pcur->get_lsb() - *icur;

      acc_off += tmp_off * acc_wid;
      acc_wid *= pcur->width();

    } while (icur != indices.begin());
  }

  return acc_off;
}

bool NetNet::sb_to_slice(const list<long>& indices, long sb, long& loff,
                         unsigned long& lwid) const {
  ivl_assert(*this, indices.size() < packed_dims().size());
  return prefix_to_slice(packed_dims(), indices, sb, loff, lwid);
}

unsigned NetNet::unpacked_count() const {
  unsigned c = 1;
  for (size_t idx = 0; idx < unpacked_dims_.size(); idx += 1) {
    c *= unpacked_dims_[idx].width();
  }

  return c;
}

void NetNet::incr_eref() { eref_count_ += 1; }

void NetNet::decr_eref() {
  assert(eref_count_ > 0);
  eref_count_ -= 1;
}

unsigned NetNet::peek_eref() const { return eref_count_; }

/*
 * Test each of the bits in the range, and set them. If any bits are
 * already set then return true.
 */
bool NetNet::test_and_set_part_driver(unsigned pmsb, unsigned plsb, int widx) {
  if (lref_mask_.empty()) lref_mask_.resize(vector_width() * pin_count());

  // If indexing a word that doesn't exist, then pretend this is
  // never driven.
  if (widx < 0) return false;
  if (widx >= (int)pin_count()) return false;

  bool rc = false;
  unsigned word_base = vector_width() * widx;
  for (unsigned idx = plsb; idx <= pmsb; idx += 1) {
    if (lref_mask_[idx + word_base])
      rc = true;
    else
      lref_mask_[idx + word_base] = true;
  }

  return rc;
}

void NetNet::incr_lref() { lref_count_ += 1; }

void NetNet::decr_lref() {
  assert(lref_count_ > 0);
  lref_count_ -= 1;
}

unsigned NetNet::get_refs() const { return lref_count_ + eref_count_; }

void NetNet::add_delay_path(NetDelaySrc* path) { delay_paths_.push_back(path); }

unsigned NetNet::delay_paths(void) const { return delay_paths_.size(); }

const NetDelaySrc* NetNet::delay_path(unsigned idx) const {
  assert(idx < delay_paths_.size());
  return delay_paths_[idx];
}

NetPartSelect::NetPartSelect(NetNet* sig, unsigned off, unsigned wid,
                             NetPartSelect::dir_t dir__, bool signed_flag__)
    : NetNode(sig->scope(), sig->scope()->local_symbol(), 2),
      off_(off),
      wid_(wid),
      dir_(dir__),
      signed_flag_(signed_flag__) {
  set_line(*sig);

  switch (dir_) {
    case NetPartSelect::VP:
      pin(0).set_dir(Link::OUTPUT);
      pin(1).set_dir(Link::INPUT);
      break;
    case NetPartSelect::PV:
      pin(0).set_dir(Link::INPUT);
      pin(1).set_dir(Link::OUTPUT);
      break;
  }

  connect(pin(1), sig->pin(0));
}

NetPartSelect::NetPartSelect(NetNet* sig, NetNet* sel, unsigned wid,
                             bool signed_flag__)
    : NetNode(sig->scope(), sig->scope()->local_symbol(), 3),
      off_(0),
      wid_(wid),
      dir_(VP),
      signed_flag_(signed_flag__) {
  switch (dir_) {
    case NetPartSelect::VP:
      pin(0).set_dir(Link::OUTPUT);
      pin(1).set_dir(Link::INPUT);
      break;
    case NetPartSelect::PV:
      /* Only a vector to part can be a variable select. */
      assert(0);
  }
  pin(2).set_dir(Link::INPUT);

  connect(pin(1), sig->pin(0));
  connect(pin(2), sel->pin(0));
}

NetPartSelect::~NetPartSelect() {}

unsigned NetPartSelect::width() const { return wid_; }

unsigned NetPartSelect::base() const { return off_; }

NetSubstitute::NetSubstitute(NetNet* sig, NetNet* sub, unsigned wid,
                             unsigned off)
    : NetNode(sig->scope(), sig->scope()->local_symbol(), 3),
      wid_(wid),
      off_(off) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
  pin(2).set_dir(Link::INPUT);
  connect(pin(1), sig->pin(0));
  connect(pin(2), sub->pin(0));
}

NetSubstitute::~NetSubstitute() {}

NetProc::NetProc() : next_(0) {}

NetProc::~NetProc() {}

NetProcTop::NetProcTop(NetScope* s, ivl_process_type_t t, NetProc* st)
    : type_(t), statement_(st), scope_(s) {
  synthesized_design_ = 0;
}

NetProcTop::~NetProcTop() {
  if (!synthesized_design_) {
    delete statement_;
    return;
  }

  NexusSet nex_set;
  statement_->nex_output(nex_set);

  delete statement_;

  bool flag = false;
  for (unsigned idx = 0; idx < nex_set.size(); idx += 1) {
    NetNet* net = nex_set[idx].lnk.nexus()->pick_any_net();
    if (net->peek_lref() > 0) {
      cerr << get_fileline() << ": warning: '" << net->name()
           << "' is driven by more than one process." << endl;
      flag = true;
    }
  }
  if (flag) {
    cerr << get_fileline()
         << ": sorry: Cannot synthesize signals "
            "that are driven by more than one process."
         << endl;
    synthesized_design_->errors += 1;
  }
}

NetProc* NetProcTop::statement() { return statement_; }

const NetProc* NetProcTop::statement() const { return statement_; }

NetScope* NetProcTop::scope() { return scope_; }

const NetScope* NetProcTop::scope() const { return scope_; }

NetAnalogTop::NetAnalogTop(NetScope* scope__, ivl_process_type_t t, NetProc* st)
    : type_(t), statement_(st), scope_(scope__) {
  next_ = 0;
}

NetAnalogTop::~NetAnalogTop() {}

NetProc* NetAnalogTop::statement() { return statement_; }

const NetProc* NetAnalogTop::statement() const { return statement_; }

NetScope* NetAnalogTop::scope() { return scope_; }

const NetScope* NetAnalogTop::scope() const { return scope_; }

NetCastInt2::NetCastInt2(NetScope* scope__, perm_string n, unsigned width__)
    : NetNode(scope__, n, 2), width_(width__) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetCastInt4::NetCastInt4(NetScope* scope__, perm_string n, unsigned width__)
    : NetNode(scope__, n, 2), width_(width__) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetCastReal::NetCastReal(NetScope* scope__, perm_string n, bool signed_flag__)
    : NetNode(scope__, n, 2), signed_flag_(signed_flag__) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetConcat::NetConcat(NetScope* scope__, perm_string n, unsigned wid,
                     unsigned cnt, bool trans_flag)
    : NetNode(scope__, n, cnt + 1), width_(wid), transparent_(trans_flag) {
  pin(0).set_dir(Link::OUTPUT);
  for (unsigned idx = 1; idx < cnt + 1; idx += 1) {
    pin(idx).set_dir(Link::INPUT);
  }
}

NetConcat::~NetConcat() {}

unsigned NetConcat::width() const { return width_; }

NetReplicate::NetReplicate(NetScope* scope__, perm_string n, unsigned wid,
                           unsigned rpt)
    : NetNode(scope__, n, 2), width_(wid), repeat_(rpt) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetReplicate::~NetReplicate() {}

unsigned NetReplicate::width() const { return width_; }

unsigned NetReplicate::repeat() const { return repeat_; }

/*
 * The NetFF class represents an LPM_FF device. The pinout is assigned
 * like so:
 *    0  -- Clock
 *    1  -- Enable
 *    2  -- Aset
 *    3  -- Aclr
 *    4  -- Sset
 *    5  -- Sclr
 *    6  -- Data
 *    7  -- Q
 *     ...
 */

NetFF::NetFF(NetScope* s, perm_string n, bool negedge__, unsigned width__)
    : NetNode(s, n, 8), negedge_(negedge__), width_(width__) {
  pin_Clock().set_dir(Link::INPUT);
  pin_Enable().set_dir(Link::INPUT);
  pin_Aset().set_dir(Link::INPUT);
  pin_Aclr().set_dir(Link::INPUT);
  pin_Sset().set_dir(Link::INPUT);
  pin_Sclr().set_dir(Link::INPUT);
  pin_Data().set_dir(Link::INPUT);
  pin_Q().set_dir(Link::OUTPUT);
}

NetFF::~NetFF() {}

bool NetFF::is_negedge() const { return negedge_; }

unsigned NetFF::width() const { return width_; }

Link& NetFF::pin_Clock() { return pin(0); }

const Link& NetFF::pin_Clock() const { return pin(0); }

Link& NetFF::pin_Enable() { return pin(1); }

const Link& NetFF::pin_Enable() const { return pin(1); }

Link& NetFF::pin_Aset() { return pin(2); }

const Link& NetFF::pin_Aset() const { return pin(2); }

Link& NetFF::pin_Aclr() { return pin(3); }

const Link& NetFF::pin_Aclr() const { return pin(3); }

Link& NetFF::pin_Sset() { return pin(4); }

const Link& NetFF::pin_Sset() const { return pin(4); }

Link& NetFF::pin_Sclr() { return pin(5); }

const Link& NetFF::pin_Sclr() const { return pin(5); }

Link& NetFF::pin_Data() { return pin(6); }

const Link& NetFF::pin_Data() const { return pin(6); }

Link& NetFF::pin_Q() { return pin(7); }

const Link& NetFF::pin_Q() const { return pin(7); }

void NetFF::aset_value(const verinum& val) { aset_value_ = val; }

const verinum& NetFF::aset_value() const { return aset_value_; }

void NetFF::sset_value(const verinum& val) { sset_value_ = val; }

const verinum& NetFF::sset_value() const { return sset_value_; }

/*
 * The NetLatch class represents an LPM_LATCH device. The pinout is assigned
 * like so:
 *    0  -- Enable
 *    1  -- Data
 *    2  -- Q
 */

NetLatch::NetLatch(NetScope* s, perm_string n, unsigned width__)
    : NetNode(s, n, 3), width_(width__) {
  pin_Enable().set_dir(Link::INPUT);
  pin_Data().set_dir(Link::INPUT);
  pin_Q().set_dir(Link::OUTPUT);
}

NetLatch::~NetLatch() {}

unsigned NetLatch::width() const { return width_; }

Link& NetLatch::pin_Enable() { return pin(0); }

const Link& NetLatch::pin_Enable() const { return pin(0); }

Link& NetLatch::pin_Data() { return pin(1); }

const Link& NetLatch::pin_Data() const { return pin(1); }

Link& NetLatch::pin_Q() { return pin(2); }

const Link& NetLatch::pin_Q() const { return pin(2); }

NetAbs::NetAbs(NetScope* s, perm_string n, unsigned w)
    : NetNode(s, n, 2), width_(w) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetAbs::~NetAbs() {}

unsigned NetAbs::width() const { return width_; }

/*
 * The NetAddSub class represents an LPM_ADD_SUB device. The pinout is
 * assigned like so:
 *    0  -- Cout
 *    1  -- DataA (normally a vector)
 *    2  -- DataB (normally a vector)
 *    3  -- Result (normally a vector)
 */
NetAddSub::NetAddSub(NetScope* s, perm_string n, unsigned w)
    : NetNode(s, n, 4), width_(w) {
  pin(0).set_dir(Link::OUTPUT);  // Cout
  pin(1).set_dir(Link::INPUT);   // DataA
  pin(2).set_dir(Link::INPUT);   // DataB
  pin(3).set_dir(Link::OUTPUT);  // Result
}

NetAddSub::~NetAddSub() {}

unsigned NetAddSub::width() const { return width_; }

Link& NetAddSub::pin_Cout() { return pin(0); }

const Link& NetAddSub::pin_Cout() const { return pin(0); }

Link& NetAddSub::pin_DataA() { return pin(1); }

const Link& NetAddSub::pin_DataA() const { return pin(1); }

Link& NetAddSub::pin_DataB() { return pin(2); }

const Link& NetAddSub::pin_DataB() const { return pin(2); }

Link& NetAddSub::pin_Result() { return pin(3); }

const Link& NetAddSub::pin_Result() const { return pin(3); }

NetArrayDq::NetArrayDq(NetScope* s, perm_string n, NetNet* mem__, unsigned awid)
    : NetNode(s, n, 2), mem_(mem__), awidth_(awid) {
  pin(0).set_dir(Link::OUTPUT);  // Result
  pin(1).set_dir(
      Link::INPUT);  // Address
                     // Increment the expression reference count for the target
                     // memory so that it is not deleted underneath me.
  mem_->incr_eref();
}

NetArrayDq::~NetArrayDq() {}

unsigned NetArrayDq::width() const { return mem_->vector_width(); }

unsigned NetArrayDq::awidth() const { return awidth_; }

const NetNet* NetArrayDq::mem() const { return mem_; }

Link& NetArrayDq::pin_Result() { return pin(0); }

Link& NetArrayDq::pin_Address() { return pin(1); }

const Link& NetArrayDq::pin_Result() const { return pin(0); }

const Link& NetArrayDq::pin_Address() const { return pin(1); }

/*
 * The pinout for the NetCLShift is:
 *    0  -- Result
 *    1  -- Data
 *    2  -- Distance
 */
NetCLShift::NetCLShift(NetScope* s, perm_string n, unsigned width__,
                       unsigned width_dist__, bool right_flag__,
                       bool signed_flag__)
    : NetNode(s, n, 3),
      width_(width__),
      width_dist_(width_dist__),
      right_flag_(right_flag__),
      signed_flag_(signed_flag__) {
  pin(0).set_dir(Link::OUTPUT);  // Result
  pin(1).set_dir(Link::INPUT);   // Data
  pin(2).set_dir(Link::INPUT);   // Distance
}

NetCLShift::~NetCLShift() {}

unsigned NetCLShift::width() const { return width_; }

unsigned NetCLShift::width_dist() const { return width_dist_; }

bool NetCLShift::right_flag() const { return right_flag_; }

bool NetCLShift::signed_flag() const { return signed_flag_; }

Link& NetCLShift::pin_Data() { return pin(1); }

const Link& NetCLShift::pin_Data() const { return pin(1); }

Link& NetCLShift::pin_Result() { return pin(0); }

const Link& NetCLShift::pin_Result() const { return pin(0); }

Link& NetCLShift::pin_Distance() { return pin(2); }

const Link& NetCLShift::pin_Distance() const { return pin(2); }

NetCompare::NetCompare(NetScope* s, perm_string n, unsigned wi)
    : NetNode(s, n, 8), width_(wi) {
  signed_flag_ = false;
  pin(0).set_dir(Link::OUTPUT);  // AGB
  pin(1).set_dir(Link::OUTPUT);  // AGEB
  pin(2).set_dir(Link::OUTPUT);  // AEB
  pin(3).set_dir(Link::OUTPUT);  // ANEB
  pin(4).set_dir(Link::OUTPUT);  // ALB
  pin(5).set_dir(Link::OUTPUT);  // ALEB
  pin(6).set_dir(Link::INPUT);   // DataA
  pin(7).set_dir(Link::INPUT);   // DataB
}

NetCompare::~NetCompare() {}

unsigned NetCompare::width() const { return width_; }

bool NetCompare::get_signed() const { return signed_flag_; }

void NetCompare::set_signed(bool flag) { signed_flag_ = flag; }

Link& NetCompare::pin_AGB() { return pin(0); }

const Link& NetCompare::pin_AGB() const { return pin(0); }

Link& NetCompare::pin_AGEB() { return pin(1); }

const Link& NetCompare::pin_AGEB() const { return pin(1); }

Link& NetCompare::pin_AEB() { return pin(2); }

const Link& NetCompare::pin_AEB() const { return pin(2); }

Link& NetCompare::pin_ANEB() { return pin(3); }

const Link& NetCompare::pin_ANEB() const { return pin(3); }

Link& NetCompare::pin_ALB() { return pin(4); }

const Link& NetCompare::pin_ALB() const { return pin(4); }

Link& NetCompare::pin_ALEB() { return pin(5); }

const Link& NetCompare::pin_ALEB() const { return pin(5); }

Link& NetCompare::pin_DataA() { return pin(6); }

const Link& NetCompare::pin_DataA() const { return pin(6); }

Link& NetCompare::pin_DataB() { return pin(7); }

const Link& NetCompare::pin_DataB() const { return pin(7); }

NetDivide::NetDivide(NetScope* sc, perm_string n, unsigned wr, unsigned wa,
                     unsigned wb)
    : NetNode(sc, n, 3),
      width_r_(wr),
      width_a_(wa),
      width_b_(wb),
      signed_flag_(false) {
  pin(0).set_dir(Link::OUTPUT);  // Result
  pin(1).set_dir(Link::INPUT);   // DataA
  pin(2).set_dir(Link::INPUT);   // DataB
}

NetDivide::~NetDivide() {}

unsigned NetDivide::width_r() const { return width_r_; }

unsigned NetDivide::width_a() const { return width_a_; }

unsigned NetDivide::width_b() const { return width_b_; }

void NetDivide::set_signed(bool flag) { signed_flag_ = flag; }

bool NetDivide::get_signed() const { return signed_flag_; }

Link& NetDivide::pin_Result() { return pin(0); }

const Link& NetDivide::pin_Result() const { return pin(0); }

Link& NetDivide::pin_DataA() { return pin(1); }

const Link& NetDivide::pin_DataA() const { return pin(1); }

Link& NetDivide::pin_DataB() { return pin(2); }

const Link& NetDivide::pin_DataB() const { return pin(2); }

NetLiteral::NetLiteral(NetScope* sc, perm_string n, const verireal& val)
    : NetNode(sc, n, 1), real_(val) {
  pin(0).set_dir(Link::OUTPUT);
}

NetLiteral::~NetLiteral() {}

ivl_variable_type_t NetLiteral::data_type() const { return IVL_VT_REAL; }

const verireal& NetLiteral::value_real() const { return real_; }

NetMult::NetMult(NetScope* sc, perm_string n, unsigned wr, unsigned wa,
                 unsigned wb)
    : NetNode(sc, n, 3),
      signed_(false),
      width_r_(wr),
      width_a_(wa),
      width_b_(wb) {
  pin(0).set_dir(Link::OUTPUT);  // Result
  pin(1).set_dir(Link::INPUT);   // DataA
  pin(2).set_dir(Link::INPUT);   // DataB
}

NetMult::~NetMult() {}

void NetMult::set_signed(bool flag) { signed_ = flag; }

bool NetMult::get_signed() const { return signed_; }

unsigned NetMult::width_r() const { return width_r_; }

unsigned NetMult::width_a() const { return width_a_; }

unsigned NetMult::width_b() const { return width_b_; }

Link& NetMult::pin_Result() { return pin(0); }

const Link& NetMult::pin_Result() const { return pin(0); }

Link& NetMult::pin_DataA() { return pin(1); }

const Link& NetMult::pin_DataA() const { return pin(1); }

Link& NetMult::pin_DataB() { return pin(2); }

const Link& NetMult::pin_DataB() const { return pin(2); }

NetPow::NetPow(NetScope* sc, perm_string n, unsigned wr, unsigned wa,
               unsigned wb)
    : NetNode(sc, n, 3),
      signed_(false),
      width_r_(wr),
      width_a_(wa),
      width_b_(wb) {
  pin(0).set_dir(Link::OUTPUT);  // Result
  pin(1).set_dir(Link::INPUT);   // DataA
  pin(2).set_dir(Link::INPUT);   // DataB
}

NetPow::~NetPow() {}

void NetPow::set_signed(bool flag) { signed_ = flag; }

bool NetPow::get_signed() const { return signed_; }

unsigned NetPow::width_r() const { return width_r_; }

unsigned NetPow::width_a() const { return width_a_; }

unsigned NetPow::width_b() const { return width_b_; }

Link& NetPow::pin_Result() { return pin(0); }

const Link& NetPow::pin_Result() const { return pin(0); }

Link& NetPow::pin_DataA() { return pin(1); }

const Link& NetPow::pin_DataA() const { return pin(1); }

Link& NetPow::pin_DataB() { return pin(2); }

const Link& NetPow::pin_DataB() const { return pin(2); }

/*
 * The NetMux class represents an LPM_MUX device. The pinout is assigned
 * like so:
 *    0   -- Result
 *    1   -- Sel
 *    2+N -- Data[N]  (N is the size of the mux)
 */

NetMux::NetMux(NetScope* s, perm_string n, unsigned wi, unsigned si,
               unsigned sw)
    : NetNode(s, n, 2 + si), width_(wi), size_(si), swidth_(sw) {
  pin(0).set_dir(Link::OUTPUT);  // Q
  pin(1).set_dir(Link::INPUT);   // Sel

  for (unsigned idx = 0; idx < size_; idx += 1) {
    pin_Data(idx).set_dir(Link::INPUT);  // Data[idx]
  }
}

NetMux::~NetMux() {}

unsigned NetMux::width() const { return width_; }

unsigned NetMux::size() const { return size_; }

unsigned NetMux::sel_width() const { return swidth_; }

Link& NetMux::pin_Result() { return pin(0); }

const Link& NetMux::pin_Result() const { return pin(0); }

Link& NetMux::pin_Sel() { return pin(1); }

const Link& NetMux::pin_Sel() const { return pin(1); }

Link& NetMux::pin_Data(unsigned s) {
  assert(s < size_);
  return pin(2 + s);
}

const Link& NetMux::pin_Data(unsigned s) const {
  assert(s < size_);
  return pin(2 + s);
}

NetSignExtend::NetSignExtend(NetScope* s, perm_string n, unsigned w)
    : NetNode(s, n, 2), width_(w) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetSignExtend::~NetSignExtend() {}

unsigned NetSignExtend::width() const { return width_; }

NetBUFZ::NetBUFZ(NetScope* s, perm_string n, unsigned w, bool trans)
    : NetNode(s, n, 2), width_(w), transparent_(trans) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetBUFZ::~NetBUFZ() {}

unsigned NetBUFZ::width() const { return width_; }

NetCaseCmp::NetCaseCmp(NetScope* s, perm_string n, unsigned wid, kind_t k)
    : NetNode(s, n, 3), width_(wid), kind_(k) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
  pin(2).set_dir(Link::INPUT);
}

NetCaseCmp::~NetCaseCmp() {}

unsigned NetCaseCmp::width() const { return width_; }

NetCondit::NetCondit(NetExpr* ex, NetProc* i, NetProc* e)
    : expr_(ex), if_(i), else_(e) {}

NetCondit::~NetCondit() {
  delete expr_;
  delete if_;
  delete else_;
}

const NetExpr* NetCondit::expr() const { return expr_; }

NetExpr* NetCondit::expr() { return expr_; }

void NetCondit::set_expr(NetExpr* ex) {
  delete expr_;
  expr_ = ex;
}

NetProc* NetCondit::if_clause() { return if_; }

NetProc* NetCondit::else_clause() { return else_; }

NetConst::NetConst(NetScope* s, perm_string n, verinum::V v)
    : NetNode(s, n, 1), value_(v, 1) {
  pin(0).set_dir(Link::OUTPUT);
}

NetConst::NetConst(NetScope* s, perm_string n, const verinum& val)
    : NetNode(s, n, 1), value_(val) {
  pin(0).set_dir(Link::OUTPUT);
}

NetConst::~NetConst() {}

verinum::V NetConst::value(unsigned idx) const {
  assert(idx < width());
  return value_[idx];
}

NetBaseDef::NetBaseDef(NetScope* s, const vector<NetNet*>& po,
                       const std::vector<NetExpr*>& pd)
    : scope_(s), ports_(po), pdefaults_(pd) {
  proc_ = 0;
}

NetBaseDef::~NetBaseDef() {}

const NetScope* NetBaseDef::scope() const { return scope_; }

NetScope* NetBaseDef::scope() { return scope_; }

unsigned NetBaseDef::port_count() const { return ports_.size(); }

NetNet* NetBaseDef::port(unsigned idx) const {
  assert(idx < ports_.size());
  return ports_[idx];
}

NetExpr* NetBaseDef::port_defe(unsigned idx) const {
  assert(idx < pdefaults_.size());
  return pdefaults_[idx];
}

void NetBaseDef::set_proc(NetProc* st) {
  assert(proc_ == 0);
  assert(st != 0);
  proc_ = st;
}

const NetProc* NetBaseDef::proc() const { return proc_; }

NetFuncDef::NetFuncDef(NetScope* s, NetNet* result, const vector<NetNet*>& po,
                       const vector<NetExpr*>& pd)
    : NetBaseDef(s, po, pd), result_sig_(result) {}

NetFuncDef::~NetFuncDef() {}

const NetNet* NetFuncDef::return_sig() const { return result_sig_; }

NetSTask::NetSTask(const char* na, ivl_sfunc_as_task_t sfat,
                   const vector<NetExpr*>& pa)
    : name_(0), sfunc_as_task_(sfat), parms_(pa) {
  name_ = lex_strings.add(na);
  assert(name_[0] == '$');
}

NetSTask::~NetSTask() {
  for (unsigned idx = 0; idx < parms_.size(); idx += 1) delete parms_[idx];

  /* The name_ string is perm-allocated in lex_strings. */
}

const char* NetSTask::name() const { return name_; }

ivl_sfunc_as_task_t NetSTask::sfunc_as_task() const { return sfunc_as_task_; }

unsigned NetSTask::nparms() const { return parms_.size(); }

const NetExpr* NetSTask::parm(unsigned idx) const { return parms_[idx]; }

NetEUFunc::NetEUFunc(NetScope* scope, NetScope* def, NetESignal* res,
                     vector<NetExpr*>& p, bool nc)
    : scope_(scope), func_(def), result_sig_(res), parms_(p), need_const_(nc) {
  expr_width(result_sig_->expr_width());
}

NetEUFunc::~NetEUFunc() {
  for (unsigned idx = 0; idx < parms_.size(); idx += 1) delete parms_[idx];
}
#if 0
const string NetEUFunc::name() const
{
      return func_->name();
}
#endif
const NetESignal* NetEUFunc::result_sig() const { return result_sig_; }

unsigned NetEUFunc::parm_count() const { return parms_.size(); }

const NetExpr* NetEUFunc::parm(unsigned idx) const {
  assert(idx < parms_.size());
  return parms_[idx];
}

const NetScope* NetEUFunc::func() const { return func_; }

ivl_variable_type_t NetEUFunc::expr_type() const {
  if (result_sig_) return result_sig_->expr_type();

  return IVL_VT_VOID;
}

NetUTask::NetUTask(NetScope* def) : task_(def) {}

NetUTask::~NetUTask() {}

const NetScope* NetUTask::task() const { return task_; }

NetAlloc::NetAlloc(NetScope* scope__) : scope_(scope__) {}

NetAlloc::~NetAlloc() {}
#if 0
const string NetAlloc::name() const
{
      return scope_->name();
}
#endif
const NetScope* NetAlloc::scope() const { return scope_; }

NetFree::NetFree(NetScope* scope__) : scope_(scope__) {}

NetFree::~NetFree() {}
#if 0
const string NetFree::name() const
{
      return scope_->name();
}
#endif
const NetScope* NetFree::scope() const { return scope_; }

/*
 * Create a bitwise operator node from the opcode and the left and
 * right expressions.
 */
NetEBBits::NetEBBits(char op__, NetExpr* l, NetExpr* r, unsigned wid,
                     bool signed_flag)
    : NetEBinary(op__, l, r, wid, signed_flag) {}

NetEBBits::~NetEBBits() {}

NetEBinary::NetEBinary(char op__, NetExpr* l, NetExpr* r, unsigned wid,
                       bool signed_flag)
    : op_(op__), left_(l), right_(r) {
  expr_width(wid);
  cast_signed_base_(signed_flag);
}

NetEBinary::~NetEBinary() {
  delete left_;
  delete right_;
}

bool NetEBinary::has_width() const {
  return left_->has_width() && right_->has_width();
}

NetEBLogic::NetEBLogic(char op__, NetExpr* l, NetExpr* r)
    : NetEBinary(op__, l, r, 1, false) {}

NetEBLogic::~NetEBLogic() {}

NetEConst::NetEConst(const verinum& val) : NetExpr(val.len()), value_(val) {
  cast_signed_base_(value_.has_sign());
}

NetEConst::~NetEConst() {}

void NetEConst::cast_signed(bool flag) {
  cast_signed_base_(flag);
  value_.has_sign(flag);
}

const verinum& NetEConst::value() const { return value_; }

bool NetEConst::has_width() const { return value_.has_len(); }

ivl_variable_type_t NetEConst::expr_type() const {
  if (value_.len() == 0) return IVL_VT_LOGIC;
  if (value_.is_string()) return IVL_VT_BOOL;
  if (value_.is_defined()) return IVL_VT_BOOL;

  return IVL_VT_LOGIC;
}

void NetEConst::trim()

{
  if (value_.is_string()) return;

  value_.has_len(false);
  value_ = trim_vnum(value_);
  expr_width(value_.len());
}

NetEConstParam::NetEConstParam(NetScope* s, perm_string n, const verinum& v)
    : NetEConst(v), scope_(s), name_(n) {
  cast_signed_base_(v.has_sign());
}

NetEConstParam::~NetEConstParam() {}

perm_string NetEConstParam::name() const { return name_; }

const NetScope* NetEConstParam::scope() const { return scope_; }

NetEEvent::NetEEvent(NetEvent* e) : event_(e) { e->exprref_ += 1; }

NetEEvent::~NetEEvent() {}

const NetEvent* NetEEvent::event() const { return event_; }

NetEScope::NetEScope(NetScope* s) : scope_(s) {}

NetEScope::~NetEScope() {}

const NetScope* NetEScope::scope() const { return scope_; }

NetESignal::NetESignal(NetNet* n)
    : NetExpr(n->vector_width()),
      net_(n),
      enum_type_(n->enumeration()),
      word_(0) {
  net_->incr_eref();
  set_line(*n);
  cast_signed_base_(net_->get_signed());
}

NetESignal::NetESignal(NetNet* n, NetExpr* w)
    : NetExpr(n->vector_width()), net_(n), word_(w) {
  net_->incr_eref();
  set_line(*n);
  cast_signed_base_(net_->get_signed());
}

NetESignal::~NetESignal() { net_->decr_eref(); }

perm_string NetESignal::name() const { return net_->name(); }

const netenum_t* NetESignal::enumeration() const { return enum_type_; }

const NetExpr* NetESignal::word_index() const { return word_; }

unsigned NetESignal::vector_width() const { return net_->vector_width(); }

const NetNet* NetESignal::sig() const { return net_; }

NetNet* NetESignal::sig() { return net_; }

/*
 * The lsi() and msi() methods should be removed from the NetESignal
 * class, to be replaced with packed dimensions aware methods of
 * getting at dimensions.
 */
long NetESignal::lsi() const {
  const vector<netrange_t>& packed = net_->packed_dims();
  ivl_assert(*this, packed.size() == 1);
  return packed.back().get_lsb();
}

long NetESignal::msi() const {
  const vector<netrange_t>& packed = net_->packed_dims();
  ivl_assert(*this, packed.size() == 1);
  return packed.back().get_msb();
}

ivl_variable_type_t NetESignal::expr_type() const {
  if (net_->darray_type())
    return IVL_VT_DARRAY;
  else
    return net_->data_type();
}

/*
 * Make a ternary operator from all the sub-expressions. The condition
 * expression is self-determined, but the true and false expressions
 * should have the same width. NOTE: This matching of the widths really
 * has to be done in elaboration.
 */
NetETernary::NetETernary(NetExpr* c, NetExpr* t, NetExpr* f, unsigned wid,
                         bool signed_flag)
    : cond_(c), true_val_(t), false_val_(f) {
  expr_width(wid);
  cast_signed_base_(signed_flag);
}

NetETernary::~NetETernary() {
  delete cond_;
  delete true_val_;
  delete false_val_;
}

const netenum_t* NetETernary::enumeration() const {
  // If the condition can evaluate to an ambiguous value,
  // the result may be blended, and so is not guaranteed
  // to be a valid enumeration value.
  if (cond_->expr_type() != IVL_VT_BOOL) return 0;

  if (true_val_->enumeration() != false_val_->enumeration()) return 0;

  return true_val_->enumeration();
}

const NetExpr* NetETernary::cond_expr() const { return cond_; }

const NetExpr* NetETernary::true_expr() const { return true_val_; }

const NetExpr* NetETernary::false_expr() const { return false_val_; }

ivl_variable_type_t NetETernary::expr_type() const {
  ivl_assert(*this, true_val_);
  ivl_assert(*this, false_val_);
  ivl_variable_type_t tru = true_val_->expr_type();
  ivl_variable_type_t fal = false_val_->expr_type();
  ivl_variable_type_t sel = cond_->expr_type();
  if (tru == IVL_VT_LOGIC && fal == IVL_VT_BOOL) return IVL_VT_LOGIC;
  if (tru == IVL_VT_BOOL && fal == IVL_VT_LOGIC) return IVL_VT_LOGIC;
  if (sel == IVL_VT_LOGIC && (tru == IVL_VT_LOGIC || tru == IVL_VT_BOOL) &&
      (fal == IVL_VT_LOGIC || fal == IVL_VT_BOOL))
    return IVL_VT_LOGIC;
  if (tru == IVL_VT_REAL && (fal == IVL_VT_LOGIC || fal == IVL_VT_BOOL))
    return IVL_VT_REAL;
  if (fal == IVL_VT_REAL && (tru == IVL_VT_LOGIC || tru == IVL_VT_BOOL))
    return IVL_VT_REAL;

  if (tru != fal) {
    cerr << get_fileline() << ": internal error:"
         << " Unexpected ?: type clash:"
         << " tru=" << tru << ", fal=" << fal << endl;
  }
  ivl_assert(*this, tru == fal);
  return tru;
}

NetEUnary::NetEUnary(char op__, NetExpr* ex, unsigned wid, bool signed_flag)
    : NetExpr(wid), op_(op__), expr_(ex) {
  cast_signed_base_(signed_flag);
}

NetEUnary::~NetEUnary() { delete expr_; }

ivl_variable_type_t NetEUnary::expr_type() const { return expr_->expr_type(); }

NetEUBits::NetEUBits(char op__, NetExpr* ex, unsigned wid, bool signed_flag)
    : NetEUnary(op__, ex, wid, signed_flag) {}

NetEUBits::~NetEUBits() {}

ivl_variable_type_t NetEUBits::expr_type() const { return expr_->expr_type(); }

NetEUReduce::NetEUReduce(char op__, NetExpr* ex)
    : NetEUnary(op__, ex, 1, false) {}

NetEUReduce::~NetEUReduce() {}

ivl_variable_type_t NetEUReduce::expr_type() const {
  return expr_->expr_type();
}

NetECast::NetECast(char op__, NetExpr* ex, unsigned wid, bool signed_flag)
    : NetEUnary(op__, ex, wid, signed_flag) {}

NetECast::~NetECast() {}

ivl_variable_type_t NetECast::expr_type() const {
  ivl_variable_type_t ret = IVL_VT_NO_TYPE;
  switch (op_) {
    case 'v':
      ret = IVL_VT_LOGIC;
      break;
    case 'r':
      ret = IVL_VT_REAL;
      break;
    case '2':
      ret = IVL_VT_BOOL;
      break;
    default:
      assert(0);
  }

  return ret;
}

NetLogic::NetLogic(NetScope* s, perm_string n, unsigned pins, TYPE t,
                   unsigned wid, bool is_cassign__)
    : NetNode(s, n, pins), type_(t), width_(wid), is_cassign_(is_cassign__) {
  pin(0).set_dir(Link::OUTPUT);
  for (unsigned idx = 1; idx < pins; idx += 1) {
    pin(idx).set_dir(Link::INPUT);
  }
}

NetLogic::TYPE NetLogic::type() const { return type_; }

unsigned NetLogic::width() const { return width_; }

bool NetLogic::is_cassign() const { return is_cassign_; }

NetUReduce::NetUReduce(NetScope* scope__, perm_string n, NetUReduce::TYPE t,
                       unsigned wid)
    : NetNode(scope__, n, 2), type_(t), width_(wid) {
  pin(0).set_dir(Link::OUTPUT);
  pin(1).set_dir(Link::INPUT);
}

NetUReduce::TYPE NetUReduce::type() const { return type_; }

unsigned NetUReduce::width() const { return width_; }

NetTaskDef::NetTaskDef(NetScope* n, const vector<NetNet*>& po,
                       const vector<NetExpr*>& pd)
    : NetBaseDef(n, po, pd) {}

NetTaskDef::~NetTaskDef() { delete proc_; }

/*
 * These are the delay_type() functions. They are used to determine
 * the type of delay for the given object.
 */

/*
 * This function implements the following table:
 *
 * in_A  in_B   out
 *   NO    NO    NO
 *   NO  ZERO  ZERO
 *   NO   POS   POS
 *   NO   DEF   POS
 * ZERO    NO  ZERO
 * ZERO  ZERO  ZERO
 * ZERO   POS   POS
 * ZERO   DEF   POS
 *  POS    NO   POS
 *  POS  ZERO   POS
 *  POS   POS   POS
 *  POS   DEF   POS
 *  DEF    NO   POS
 *  DEF  ZERO   POS
 *  DEF   POS   POS
 *  DEF   DEF   DEF
 *
 * It is used to combine two delay values.
 */
static DelayType combine_delays(const DelayType a, const DelayType b) {
  /* The default is POSSIBLE_DELAY. */
  DelayType result = POSSIBLE_DELAY;

  /* If both are no or zero delay then we return ZERO_DELAY. */
  if ((a == NO_DELAY || a == ZERO_DELAY) &&
      (b == NO_DELAY || b == ZERO_DELAY)) {
    result = ZERO_DELAY;
  }

  /* Except if both are no delay then we return NO_DELAY. */
  if (a == NO_DELAY && b == NO_DELAY) {
    result = NO_DELAY;
  }

  /* If both are definite delay then we return DEFINITE_DELAY. */
  if (a == DEFINITE_DELAY && b == DEFINITE_DELAY) {
    result = DEFINITE_DELAY;
  }

  return result;
}

/*
 * This is used to see what we can find out about the delay when it
 * is given as an expression. We also use this for loop expressions.
 */
static DelayType delay_type_from_expr(const NetExpr* expr) {
  DelayType result = POSSIBLE_DELAY;

  if (const NetEConst* e = dynamic_cast<const NetEConst*>(expr)) {
    if (e->value().is_zero())
      result = ZERO_DELAY;
    else
      result = DEFINITE_DELAY;
  }

  if (const NetECReal* e = dynamic_cast<const NetECReal*>(expr)) {
    if (e->value().as_double() == 0.0)
      result = ZERO_DELAY;
    else
      result = DEFINITE_DELAY;
  }

  return result;
}

/*
 * The looping structures can use the same basic code so put it here
 * instead of duplicating it for each one (repeat and while).
 */
static DelayType get_loop_delay_type(const NetExpr* expr, const NetProc* proc,
                                     bool print_delay) {
  DelayType result;

  switch (delay_type_from_expr(expr)) {
      /* We have a constant false expression so the body never runs. */
    case ZERO_DELAY:
      result = NO_DELAY;
      break;
      /* We have a constant true expression so the body always runs. */
    case DEFINITE_DELAY:
      if (proc) {
        result = proc->delay_type(print_delay);
      } else {
        result = NO_DELAY;
      }
      break;
      /* We don't know if the body will run so reduce a DEFINITE_DELAY
       * to a POSSIBLE_DELAY. All other stay the same. */
    case POSSIBLE_DELAY:
      if (proc) {
        result = combine_delays(NO_DELAY, proc->delay_type(print_delay));
      } else {
        result = NO_DELAY;
      }
      break;
      /* This should never happen since delay_type_from_expr() only
       * returns three different values. */
    default:
      result = NO_DELAY;
      assert(0);
  }

  return result;
}

/* The default object does not have any delay. */
DelayType NetProc::delay_type(bool /* print_delay */) const { return NO_DELAY; }

DelayType NetBlock::delay_type(bool print_delay) const {
  // A join_none has no delay.
  if (type() == PARA_JOIN_NONE) return NO_DELAY;

  DelayType result;
  // A join_any has the minimum delay.
  if (type() == PARA_JOIN_ANY) {
    result = DEFINITE_DELAY;
    for (const NetProc* cur = proc_first(); cur; cur = proc_next(cur)) {
      DelayType dt = cur->delay_type(print_delay);
      if (dt < result) result = dt;
      if ((dt == NO_DELAY) && !print_delay) break;
    }

    // A begin or join has the maximum delay.
  } else {
    result = NO_DELAY;
    for (const NetProc* cur = proc_first(); cur; cur = proc_next(cur)) {
      DelayType dt = cur->delay_type(print_delay);
      if (dt > result) result = dt;
      if ((dt == DEFINITE_DELAY) && !print_delay) break;
    }
  }

  return result;
}

DelayType NetCase::delay_type(bool print_delay) const {
  DelayType result = NO_DELAY;
  bool def_stmt = false;
  unsigned nstmts = nitems();

  for (unsigned idx = 0; idx < nstmts; idx += 1) {
    if (!expr(idx)) def_stmt = true;
    DelayType dt = stat(idx) ? stat(idx)->delay_type(print_delay) : NO_DELAY;
    if (idx == 0) {
      result = dt;
    } else {
      result = combine_delays(result, dt);
    }
  }

  // FIXME: If all the cases are covered (e.g. an enum) then this is not true.
  /* If we don't have a default statement we don't know for sure
   * that we have a delay. */
  if (!def_stmt) result = combine_delays(NO_DELAY, result);

  return result;
}

DelayType NetCondit::delay_type(bool print_delay) const {
  DelayType if_type = if_ ? if_->delay_type(print_delay) : NO_DELAY;
  DelayType el_type = else_ ? else_->delay_type(print_delay) : NO_DELAY;
  return combine_delays(if_type, el_type);
}

/*
 * A do/while will execute the body at least once.
 */
DelayType NetDoWhile::delay_type(bool print_delay) const {
  if (proc_) return proc_->delay_type(print_delay);

  return ZERO_DELAY;
}

DelayType NetEvWait::delay_type(bool print_delay) const {
  if (print_delay) {
    cerr << get_fileline()
         << ": error: an event control is not allowed "
            "in an always_comb, always_ff or always_latch process."
         << endl;
  }

  return DEFINITE_DELAY;
}

DelayType NetForever::delay_type(bool print_delay) const {
  if (statement_) return statement_->delay_type(print_delay);

  return ZERO_DELAY;
}

DelayType NetForLoop::delay_type(bool print_delay) const {
  return get_loop_delay_type(condition_, statement_, print_delay);
}

DelayType NetPDelay::delay_type(bool print_delay) const {
  if (print_delay) {
    cerr << get_fileline()
         << ": error: a blocking delay is not allowed "
            "in an always_comb, always_ff or always_latch process."
         << endl;
  }

  if (expr_) {
    if (statement_) {
      return combine_delays(delay_type_from_expr(expr_),
                            statement_->delay_type(print_delay));
    } else {
      return delay_type_from_expr(expr_);
    }
  }

  if (delay() > 0) return DEFINITE_DELAY;

  if (statement_) {
    return combine_delays(ZERO_DELAY, statement_->delay_type(print_delay));
  } else {
    return ZERO_DELAY;
  }
}

DelayType NetRepeat::delay_type(bool print_delay) const {
  return get_loop_delay_type(expr_, statement_, print_delay);
}

DelayType NetTaskDef::delay_type(bool print_delay) const {
  if (proc_) {
    return proc_->delay_type(print_delay);
  } else {
    return NO_DELAY;
  }
}

DelayType NetUTask::delay_type(bool print_delay) const {
  return task()->task_def()->delay_type(print_delay);
}

static bool do_expr_event_match(const NetExpr* expr, const NetEvWait* evwt) {
  // The event wait should only have a single event.
  if (evwt->nevents() != 1) return false;
  // The event should have a single probe.
  const NetEvent* evt = evwt->event(0);
  if (evt->nprobe() != 1) return false;
  // The probe should be for any edge.
  const NetEvProbe* prb = evt->probe(0);
  if (prb->edge() != NetEvProbe::ANYEDGE) return false;
  // Create a NexusSet from the event probe signals.
  NexusSet* ns_evwt = new NexusSet;
  for (unsigned idx = 0; idx < prb->pin_count(); idx += 1) {
    if (!prb->pin(idx).is_linked()) {
      delete ns_evwt;
      return false;
    }
    // Casting away const is safe since this nexus set is only being read.
    ns_evwt->add(const_cast<Nexus*>(prb->pin(idx).nexus()), 0,
                 prb->pin(idx).nexus()->vector_width());
  }
  // Get the NexusSet for the expression.
  NexusSet* ns_expr = expr->nex_input();
  // Make sure the event and expression NexusSets match exactly.
  if (ns_evwt->size() != ns_expr->size()) {
    delete ns_evwt;
    delete ns_expr;
    return false;
  }
  ns_expr->rem(*ns_evwt);
  delete ns_evwt;
  if (ns_expr->size() != 0) {
    delete ns_expr;
    return false;
  }
  delete ns_expr;

  return true;
}

static bool while_is_wait(const NetExpr* expr, const NetProc* stmt) {
  if (const NetEvWait* evwt = dynamic_cast<const NetEvWait*>(stmt)) {
    if (evwt->statement()) return false;
    const NetEBComp* cond = dynamic_cast<const NetEBComp*>(expr);
    if (!cond) return false;
    if (cond->op() != 'N') return false;
    const NetEConst* cval = dynamic_cast<const NetEConst*>(cond->right());
    if (!cval) return false;
    const verinum val = cval->value();
    if (val.len() != 1) return false;
    if (val.get(0) != verinum::V1) return false;
    if (!do_expr_event_match(cond->left(), evwt)) return false;
    if (evwt->get_lineno() != cond->get_lineno()) return false;
    if (evwt->get_file() != cond->get_file()) return false;
    return true;
  }
  return false;
}

DelayType NetWhile::delay_type(bool print_delay) const {
  // If the wait was a constant value the compiler already removed it
  // so we know we can only have a possible delay.
  if (while_is_wait(cond_, proc_)) {
    if (print_delay) {
      cerr << get_fileline()
           << ": error: a wait statement is "
              "not allowed in an "
              "always_comb, always_ff or always_latch process."
           << endl;
    }
    return POSSIBLE_DELAY;
  }
  return get_loop_delay_type(cond_, proc_, print_delay);
}

/*
 * These are the check_synth() functions. They are used to print
 * a warning if the item is not synthesizable.
 */
static const char* get_process_type_as_string(ivl_process_type_t pr_type) {
  switch (pr_type) {
    case IVL_PR_ALWAYS_COMB:
      return "in an always_comb process.";
      break;
    case IVL_PR_ALWAYS_FF:
      return "in an always_ff process.";
      break;
    case IVL_PR_ALWAYS_LATCH:
      return "in an always_latch process.";
      break;
    default:
      assert(0);
      return 0;
  }
}

static void print_synth_warning(const NetProc* net_proc, const char* name,
                                ivl_process_type_t pr_type) {
  cerr << net_proc->get_fileline() << ": warning: " << name
       << " statement cannot be synthesized "
       << get_process_type_as_string(pr_type) << endl;
}

static void check_if_logic_l_value(const NetAssignBase* base,
                                   ivl_process_type_t pr_type) {
  if (base->l_val_count() != 1) return;

  const NetAssign_* lval = base->l_val(0);
  if (!lval) return;

  NetNet* sig = lval->sig();
  if (!sig) return;

  if ((sig->data_type() != IVL_VT_BOOL) && (sig->data_type() != IVL_VT_LOGIC)) {
    cerr << base->get_fileline()
         << ": warning: Assinging to a "
            "non-integral variable ("
         << sig->name() << ") cannot be synthesized "
         << get_process_type_as_string(pr_type) << endl;
  }
}

/* By default elements can be synthesized or ignored. */
bool NetProc::check_synth(ivl_process_type_t /* pr_type */,
                          const NetScope* /* scope */) const {
  return false;
}

// FIXME: User function calls still need to be checked (NetEUFunc).
//      : Non-constant system functions need a warning (NetESFunc).
//      : Constant functions should already be elaborated.

/* By default assign elements can be synthesized. */
bool NetAssignBase::check_synth(ivl_process_type_t /* pr_type */,
                                const NetScope* /* scope */) const {
  return false;
}

bool NetAssign::check_synth(ivl_process_type_t pr_type,
                            const NetScope* /* scope */) const {
  check_if_logic_l_value(this, pr_type);

  // FIXME: Check that ff/latch only use this for internal signals.
  return false;
}

bool NetAssignNB::check_synth(ivl_process_type_t pr_type,
                              const NetScope* /* scope */) const {
  bool result = false;
  if (pr_type == IVL_PR_ALWAYS_COMB) {
    cerr << get_fileline()
         << ": warning: A non-blocking assignment "
            "should not be used in an always_comb process."
         << endl;
  }

  if (event_) {
    cerr << get_fileline()
         << ": error: A non-blocking assignment "
            "cannot be synthesized with an event control "
         << get_process_type_as_string(pr_type) << endl;
    result = true;
  }

  check_if_logic_l_value(this, pr_type);

  return result;
}

bool NetBlock::check_synth(ivl_process_type_t pr_type,
                           const NetScope* scope) const {
  bool result = false;
  // Only a begin/end can be synthesized.
  if (type() != SEQU) {
    cerr << get_fileline() << ": error: A fork/";
    switch (type()) {
      case PARA:
        cerr << "join";
        break;
      case PARA_JOIN_ANY:
        cerr << "join_any";
        break;
      case PARA_JOIN_NONE:
        cerr << "join_none";
        break;
      default:
        assert(0);
    }
    cerr << " statement cannot be synthesized "
         << get_process_type_as_string(pr_type) << endl;
    result = true;
  }

  const NetScope* save_scope = scope;
  if (subscope()) scope = subscope();
  if (scope != save_scope) {
    result |= scope->check_synth(pr_type, scope);
  }
  for (const NetProc* cur = proc_first(); cur; cur = proc_next(cur)) {
    result |= cur->check_synth(pr_type, scope);
  }
  scope = save_scope;
  return result;
}

bool NetCase::check_synth(ivl_process_type_t pr_type,
                          const NetScope* scope) const {
  bool result = false;
  for (unsigned idx = 0; idx < nitems(); idx += 1) {
    if (stat(idx)) result |= stat(idx)->check_synth(pr_type, scope);
  }
  // FIXME: Check for ff/latch/comb structures.
  return result;
}

bool NetCAssign::check_synth(ivl_process_type_t pr_type,
                             const NetScope* /* scope */) const {
  print_synth_warning(this, "A procedural assign", pr_type);
  return false;
}

bool NetCondit::check_synth(ivl_process_type_t pr_type,
                            const NetScope* scope) const {
  bool result = false;
  if (if_) result |= if_->check_synth(pr_type, scope);
  if (else_) result |= else_->check_synth(pr_type, scope);
  // FIXME: Check for ff/latch/comb structures.
  return result;
}

bool NetDeassign::check_synth(ivl_process_type_t pr_type,
                              const NetScope* /* scope */) const {
  print_synth_warning(this, "A procedural deassign", pr_type);
  return false;
}

bool NetDisable::check_synth(ivl_process_type_t pr_type,
                             const NetScope* scope) const {
  while (scope) {
    if (scope != target_)
      scope = scope->parent();
    else
      break;
  }

  if (!scope) {
    cerr << get_fileline()
         << ": warning: A disable statement can "
            "only be synthesized when disabling an enclosing block "
         << get_process_type_as_string(pr_type) << endl;
  }
  return false;
}

bool NetDoWhile::check_synth(ivl_process_type_t pr_type,
                             const NetScope* scope) const {
  bool result = false;
  print_synth_warning(this, "A do/while", pr_type);
  if (proc_) result |= proc_->check_synth(pr_type, scope);
  return result;
}

bool NetEvTrig::check_synth(ivl_process_type_t pr_type,
                            const NetScope* /* scope */) const {
  print_synth_warning(this, "An event trigger", pr_type);
  return false;
}

// The delay check above has already marked this as an error.
bool NetEvWait::check_synth(ivl_process_type_t pr_type,
                            const NetScope* scope) const {
  bool result = false;
  if (statement_) result |= statement_->check_synth(pr_type, scope);
  return result;
}

bool NetForce::check_synth(ivl_process_type_t pr_type,
                           const NetScope* /* scope */) const {
  print_synth_warning(this, "A force", pr_type);
  return false;
}

bool NetForever::check_synth(ivl_process_type_t pr_type,
                             const NetScope* scope) const {
  bool result = false;
  print_synth_warning(this, "A forever", pr_type);
  if (statement_) result |= statement_->check_synth(pr_type, scope);
  return result;
}

/*
 * A bunch of private routines to verify that a for loop has the correct
 * structure for synthesis.
 */
static void print_for_idx_warning(const NetProc* proc, const char* check,
                                  ivl_process_type_t pr_type, NetNet* idx) {
  cerr << proc->get_fileline()
       << ": warning: A for statement must use "
          "the index ("
       << idx->name() << ") in the " << check
       << " expression to be synthesized "
       << get_process_type_as_string(pr_type) << endl;
}

static void check_for_const_synth(const NetExpr* expr, const NetProc* proc,
                                  const char* str, ivl_process_type_t pr_type) {
  if (!dynamic_cast<const NetEConst*>(expr)) {
    cerr << proc->get_fileline()
         << ": warning: A for "
            "statement must "
         << str << " value to be synthesized "
         << get_process_type_as_string(pr_type) << endl;
  }
}

static void check_for_bin_synth(const NetExpr* left, const NetExpr* right,
                                const char* str, const char* check,
                                const NetProc* proc, ivl_process_type_t pr_type,
                                NetNet* index) {
  const NetESignal* lsig = dynamic_cast<const NetESignal*>(left);
  const NetESignal* rsig = dynamic_cast<const NetESignal*>(right);

  if (lsig && (lsig->sig() == index)) {
    check_for_const_synth(right, proc, str, pr_type);
  } else if (rsig && (rsig->sig() == index)) {
    check_for_const_synth(left, proc, str, pr_type);
  } else {
    print_for_idx_warning(proc, check, pr_type, index);
  }
}

static void print_for_step_warning(const NetProc* proc,
                                   ivl_process_type_t pr_type) {
  cerr << proc->get_fileline()
       << ": warning: A for statement step must "
          "be a simple assignment statement to be synthesized "
       << get_process_type_as_string(pr_type) << endl;
}

static void print_for_step_warning(const NetProc* proc,
                                   ivl_process_type_t pr_type, NetNet* idx) {
  cerr << proc->get_fileline()
       << ": warning: A for statement step must "
          "be an assignment to the index variable ("
       << idx->name() << ") to be synthesized "
       << get_process_type_as_string(pr_type) << endl;
}

static void check_for_bstep_synth(const NetExpr* expr, const NetProc* proc,
                                  ivl_process_type_t pr_type, NetNet* index) {
  if (const NetECast* tmp = dynamic_cast<const NetECast*>(expr)) {
    expr = tmp->expr();
  }

  if (const NetEBAdd* tmp = dynamic_cast<const NetEBAdd*>(expr)) {
    check_for_bin_synth(tmp->left(), tmp->right(), "change by a constant",
                        "step", proc, pr_type, index);
  } else {
    cerr << proc->get_fileline()
         << ": warning: A for statement "
            "step must be a simple binary +/- "
            "to be synthesized "
         << get_process_type_as_string(pr_type) << endl;
  }
}

static void check_for_step_synth(const NetAssign* assign, const NetProc* proc,
                                 ivl_process_type_t pr_type, NetNet* index) {
  if (assign->l_val_count() != 1) {
    print_for_step_warning(proc, pr_type);
  } else if (assign->l_val(0)->sig() != index) {
    print_for_step_warning(proc, pr_type, index);
  } else {
    switch (assign->assign_operator()) {
      case '+':
      case '-':
        check_for_const_synth(assign->rval(), proc, "have a constant step",
                              pr_type);
        break;
      case 0:
        check_for_bstep_synth(assign->rval(), proc, pr_type, index);
        break;
      default:
        cerr << proc->get_fileline()
             << ": warning: A for statement "
                "step does not support operator '"
             << assign->assign_operator()
             << "' it must be +/- to be synthesized "
             << get_process_type_as_string(pr_type) << endl;
        break;
    }
  }
}

bool NetForLoop::check_synth(ivl_process_type_t pr_type,
                             const NetScope* scope) const {
  bool result = false;

  // FIXME: What about an enum (NetEConstEnum)?
  if (!dynamic_cast<const NetEConst*>(init_expr_)) {
    cerr << get_fileline()
         << ": warning: A for statement must "
            "have a constant initial value to be synthesized "
         << get_process_type_as_string(pr_type) << endl;
  }

  // FIXME: Do the following also need to be supported in the condition?
  //        It would seem like they are hard to use to find the bounds.
  //          From NetEBinary
  //            What about NetEBits sig & constant, etc.
  //          From NetEUnary
  //            What about NetEUBits ! sig or ! (sig == constat)
  //            What about NetEUReduce &signal
  if (const NetESignal* tmp = dynamic_cast<const NetESignal*>(condition_)) {
    if (tmp->sig() != index_) {
      print_for_idx_warning(this, "condition", pr_type, index_);
    }
  } else if (const NetEBComp* cmp =
                 dynamic_cast<const NetEBComp*>(condition_)) {
    check_for_bin_synth(cmp->left(), cmp->right(), "compare against a constant",
                        "condition", this, pr_type, index_);
  } else {
    print_for_idx_warning(this, "condition", pr_type, index_);
  }

  if (const NetAssign* tmp = dynamic_cast<const NetAssign*>(step_statement_)) {
    check_for_step_synth(tmp, this, pr_type, index_);
  } else {
    print_for_step_warning(this, pr_type);
  }

  if (statement_) result |= statement_->check_synth(pr_type, scope);
  return result;
}

// The delay check above has already marked this as an error.
bool NetPDelay::check_synth(ivl_process_type_t /* pr_type */,
                            const NetScope* /* scope */) const {
  return false;
}

bool NetRelease::check_synth(ivl_process_type_t pr_type,
                             const NetScope* /* scope */) const {
  print_synth_warning(this, "A release", pr_type);
  return false;
}

bool NetRepeat::check_synth(ivl_process_type_t pr_type,
                            const NetScope* scope) const {
  bool result = false;
  print_synth_warning(this, "A repeat", pr_type);
  if (statement_) result |= statement_->check_synth(pr_type, scope);
  return result;
}

bool NetScope::check_synth(ivl_process_type_t pr_type,
                           const NetScope* /* scope */) const {
  bool result = false;
  // Skip local events/signals
  for (NetEvent* cur = events_; cur; cur = cur->snext_) {
    if (cur->local_flag()) continue;
    cerr << cur->get_fileline() << ": warning: An event (" << cur->name()
         << ") cannot be synthesized " << get_process_type_as_string(pr_type)
         << endl;
  }
  for (signals_map_iter_t cur = signals_map_.begin(); cur != signals_map_.end();
       ++cur) {
    const NetNet* sig = cur->second;
    if ((sig->data_type() != IVL_VT_BOOL) &&
        (sig->data_type() != IVL_VT_LOGIC)) {
      cerr << sig->get_fileline()
           << ": warning: A non-integral "
              "variable ("
           << sig->name()
           << ") cannot be "
              "synthesized "
           << get_process_type_as_string(pr_type) << endl;
    }
  }
  return result;
}

bool NetSTask::check_synth(ivl_process_type_t pr_type,
                           const NetScope* /* scope */) const {
  if (strcmp(name(), "$ivl_darray_method$delete") == 0) {
    cerr << get_fileline()
         << ": warning: Dynamic array "
            "delete method cannot be synthesized "
         << get_process_type_as_string(pr_type) << endl;
  } else {
    cerr << get_fileline() << ": warning: System task (" << name()
         << ") cannot be synthesized " << get_process_type_as_string(pr_type)
         << endl;
  }
  return false;
}

bool NetTaskDef::check_synth(ivl_process_type_t pr_type,
                             const NetScope* /* scope */) const {
  bool result = false;
  const NetScope* tscope = this->scope();
  result |= tscope->check_synth(pr_type, tscope);
  if (!tscope->is_auto()) {
    cerr << tscope->get_def_file() << ":" << tscope->get_def_lineno()
         << ": warning: user task (" << tscope->basename()
         << ") must be automatic to be synthesized "
         << get_process_type_as_string(pr_type) << endl;
  }
  if (proc_) result |= proc_->check_synth(pr_type, tscope);
  return result;
}

bool NetUTask::check_synth(ivl_process_type_t pr_type,
                           const NetScope* scope) const {
  return task()->task_def()->check_synth(pr_type, scope);
}

bool NetWhile::check_synth(ivl_process_type_t pr_type,
                           const NetScope* scope) const {
  bool result = false;
  // A wait is already maked as an error in the delay check above.
  if (!while_is_wait(cond_, proc_)) {
    print_synth_warning(this, "A while", pr_type);
    if (proc_) result |= proc_->check_synth(pr_type, scope);
  }
  return result;
}
